﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using StackExchange.Redis;

namespace Jack.RedisLib
{
    public partial class RedisClient
    {
        public async Task<long?> HashDecrement(string key, string hashField, long value = 1L, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);

            return await database.HashDecrementAsync(redisKey, redisHashField, value);
        }

        public async Task<double?> HashDecrement(string key, string hashField, double value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);

            return await database.HashDecrementAsync(redisKey, redisHashField, value);
        }

        public async Task<long?> HashIncrement(string key, string hashField, long value = 1L, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);

            return await database.HashIncrementAsync(redisKey, redisHashField, value);
        }

        public async Task<double?> HashIncrement(string key, string hashField, double value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return null;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);

            return await database.HashIncrementAsync(redisKey, redisHashField, value);
        }

        public async Task<bool> HashDelete(string key, string hashField, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);

            return await database.HashDeleteAsync(redisKey, redisHashField);
        }

        public async Task<long> HashDelete(string key, List<string> hashFields, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisHashFields = BatchConvertObjectValueToRedisValue(hashFields);

            return await database.HashDeleteAsync(redisKey, redisHashFields);
        }

        public async Task<bool> HashExists(string key, string hashField, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);

            return await database.HashExistsAsync(redisKey, redisHashField);
        }

        public async Task<T?> HashGetForValueType<T>(string key, string hashField, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);
            RedisValue redisValue = await database.HashGetAsync(redisKey, redisHashField);

            return ConvertRedisValueToValueTypeValue<T>(redisValue);
        }

        public async Task<T> HashGet<T>(string key, string hashField, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);
            RedisValue redisValue = await database.HashGetAsync(redisKey, redisHashField);

            return ConvertRedisValueToObjectValue<T>(redisValue);
        }

        public async Task<Dictionary<string, T?>> HashGetForValueType<T>(string key, List<string> hashFields, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            Dictionary<string, T?> keyValues = [];
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return keyValues;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisHashFields = BatchConvertObjectValueToRedisValue(hashFields);
            RedisValue[] redisValues = await database.HashGetAsync(redisKey, redisHashFields);

            int i = 0;
            foreach (string hashField in hashFields)
            {
                keyValues[hashField] = ConvertRedisValueToValueTypeValue<T>(redisValues[i++]);
            }

            return keyValues;
        }

        public async Task<Dictionary<string, T>> HashGet<T>(string key, List<string> hashFields, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            Dictionary<string, T> keyValues = [];
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return keyValues;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisHashFields = BatchConvertObjectValueToRedisValue(hashFields);
            RedisValue[] redisValues = await database.HashGetAsync(redisKey, redisHashFields);

            int i = 0;
            foreach (string hashField in hashFields)
            {
                keyValues[hashField] = ConvertRedisValueToObjectValue<T>(redisValues[i++]);
            }

            return keyValues;
        }

        public async Task<Dictionary<string, T?>> HashGetAllForValueType<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            Dictionary<string, T?> keyValues = [];
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return keyValues;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            HashEntry[] hashEntries = await database.HashGetAllAsync(redisKey);

            if (hashEntries != null && hashEntries.Length > 0)
            {
                foreach (HashEntry hashEntry in hashEntries)
                {
                    string hashField = ConvertRedisValueToObjectValue<string>(hashEntry.Name);
                    T? hashValue = ConvertRedisValueToValueTypeValue<T>(hashEntry.Value);

                    keyValues[hashField] = hashValue;
                }
            }

            return keyValues;
        }

        public async Task<Dictionary<string, T>> HashGetAll<T>(string key, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            Dictionary<string, T> keyValues = [];
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return keyValues;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            HashEntry[] hashEntries = await database.HashGetAllAsync(redisKey);

            if (hashEntries != null && hashEntries.Length > 0)
            {
                foreach (HashEntry hashEntry in hashEntries)
                {
                    string hashField = ConvertRedisValueToObjectValue<string>(hashEntry.Name);
                    T hashValue = ConvertRedisValueToObjectValue<T>(hashEntry.Value);

                    keyValues[hashField] = hashValue;
                }
            }

            return keyValues;
        }

        public async Task<bool> HashSet<T>(string key, string hashField, T value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField);
            RedisValue redisValue = ConvertObjectValueToRedisValue(value);

            return await database.HashSetAsync(redisKey, redisHashField, redisValue);
        }

        public async Task<bool> HashSet<T>(string key, Dictionary<string, T> hashFields, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            HashEntry[] hashEntries = new HashEntry[hashFields.Count];

            int i = 0;
            foreach (KeyValuePair<string, T> hashField in hashFields)
            {
                RedisValue redisHashField = ConvertObjectValueToRedisValue(hashField.Key);
                RedisValue redisValue = ConvertObjectValueToRedisValue(hashField.Value);

                hashEntries[i++] = new HashEntry(redisHashField, redisValue);
            }

            await database.HashSetAsync(redisKey, hashEntries);
            return true;
        }

        public async Task<long> HashLength(string key, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);

            return await database.HashLengthAsync(redisKey);
        }

        public async Task<string[]> HashKeys(string key, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.HashKeysAsync(redisKey);

            string[] hashFields = redisValues.Select(x => ConvertRedisValueToObjectValue<string>(x)).ToArray();

            return hashFields;
        }

        public async IAsyncEnumerable<(string Field, T Value)> HashScan<T>(string key, string pattern = null, int pageSize = 250, long cursor = 0L, int pageOffset = 0, int db = -1, [EnumeratorCancellation] CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) yield break;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisPattern = ConvertObjectValueToRedisValue(pattern);

            await foreach (var hashEntry in database.HashScanAsync(redisKey, redisPattern, pageSize, cursor, pageOffset))
            {
                string hashField = ConvertRedisValueToObjectValue<string>(hashEntry.Name);
                T hashValue = ConvertRedisValueToObjectValue<T>(hashEntry.Value);

                yield return (hashField, hashValue);
            }
        }

        public async IAsyncEnumerable<(string Field, T? Value)> HashScanForValueType<T>(string key, string pattern = null, int pageSize = 250, long cursor = 0L, int pageOffset = 0, int db = -1, [EnumeratorCancellation] CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) yield break;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisPattern = ConvertObjectValueToRedisValue(pattern);

            await foreach (var hashEntry in database.HashScanAsync(redisKey, redisPattern, pageSize, cursor, pageOffset))
            {
                string hashField = ConvertRedisValueToObjectValue<string>(hashEntry.Name);
                T? hashValue = ConvertRedisValueToValueTypeValue<T>(hashEntry.Value);

                yield return (hashField, hashValue);
            }
        }

        public async Task<List<string>> HashRandFieldWithoutValues<T>(string key, int count, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            List<string> fields = new(count);
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return fields;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);

            // https://redis.io/commands/hrandfield/
            // HRANDFIELD key [count [WITHVALUES]]
            RedisResult redisResult = await database.ExecuteAsync("HRANDFIELD", redisKey, count);

            RedisValue[] redisResults = (RedisValue[])redisResult;
            for (int i = 0; i < redisResults.Length; i++)
            {
                fields.Add(redisResults[i]);
            }

            return fields;
        }

        public async Task<Dictionary<string, T>> HashRandFieldWithValues<T>(string key, int count, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            Dictionary<string, T> keyValues = [];
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return keyValues;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);

            // https://redis.io/commands/hrandfield/
            // HRANDFIELD key [count [WITHVALUES]]
            RedisResult redisResult = await database.ExecuteAsync("HRANDFIELD", redisKey, count, "WITHVALUES");

            RedisValue[] redisResults = (RedisValue[])redisResult;
            for (int i = 0; i < redisResults.Length; i += 2)
            {
                keyValues[redisResults[i]] = ConvertRedisValueToObjectValue<T>(redisResults[i + 1]);
            }

            return keyValues;
        }

        public async Task<Dictionary<string, T?>> HashRandFieldForValueTypeWithValues<T>(string key, int count, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            Dictionary<string, T?> keyValues = [];
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return keyValues;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);

            // https://redis.io/commands/hrandfield/
            // HRANDFIELD key [count [WITHVALUES]]
            RedisResult redisResult = await database.ExecuteAsync("HRANDFIELD", redisKey, count, "WITHVALUES");

            RedisValue[] redisResults = (RedisValue[])redisResult;
            for (int i = 0; i < redisResults.Length; i += 2)
            {
                keyValues[redisResults[i]] = ConvertRedisValueToValueTypeValue<T>(redisResults[i + 1]);
            }

            return keyValues;
        }
    }
}
